Skip to content

feat(spec, metadata-protocol): add a locale filter axis to seed datasets, composed with env - #16592

Merged
hotlong merged 7 commits into
mainfrom
claude/issue-16510-seed-locale-axis
Sep 8, 2026
Merged

feat(spec, metadata-protocol): add a locale filter axis to seed datasets, composed with env#16592
hotlong merged 7 commits into
mainfrom
claude/issue-16510-seed-locale-axis

Conversation

@hotlong

@hotlong hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #16510

Seed datasets gain a locale filter axis, composed with env by the loader. Both halves land here: the key SeedSchema accepts and the filter that enforces it.

What changed

File Change
packages/spec/src/data/seed.zod.ts SeedSchema.locale?: string[] (BCP-47 tags, LocaleSchema), .min(1), optional. Aliases locales / language / languages onto it, matching the existing environment / environments pair.
packages/spec/src/data/seed-loader.zod.ts SeedLoaderConfigSchema.locale?: string — the tag a load filters on.
packages/metadata-protocol/src/seed-loader.ts datasetAllowsLocale / isLocaleScopedDataset / normalizeLocaleTag mirror the env helpers; filterByLocale mirrors filterByEnv; filterDatasets composes the two by conjunction and replaces the single filterByEnv call site. warnOnUnresolvedLocaleScope signposts an inert axis.
packages/spec/liveness/seed.json new locale row, status experimental — see The producer gap below.
packages/metadata-protocol/src/seed-loader-locale-scope.test.ts 16 cases, shaped on seed-loader-env-scope.test.ts.
packages/spec/src/data/seed.test.ts 6 contract cases: accept, absence, empty-array rejection, bare-string rejection, alias routing.
content/docs/data-modeling/seed-data.mdx a Locale Scoping section, a best-practice entry, and the defineSeed() API block.
.changeset/seed-locale-axis.md @objectstack/spec minor, @objectstack/metadata-protocol minor.

Design decisions, all recorded in the code that carries them:

  • Omitted means every locale, and there is no default array. env can spell its default out because its three environments are a closed set; BCP-47 tags have no enumerable universe, so absence has to be the unrestricted spelling. An empty array is rejected — a dataset that applies nowhere is an authoring mistake, the same reasoning externalId's .min(1) already carries.
  • Tags compare case-insensitively, and otherwise exactly. RFC 5646 casing is a convention, not part of a tag's identity, so zh-cn and zh-CN are one tag. ['zh'] does not match zh-CN: widening that would be the lenient consumer-side fallback Prime Directive Add comprehensive test suite for Zod schema validation #12 forbids, and the author's remedy is to list both tags.
  • The axes compose by conjunction, in separate functions with separate log lines. The two answer different operator questions ("why are my demo rows missing in production" vs "why did the Chinese dataset load"), and one merged message would have to name a reason it did not measure.
  • Permissive when indeterminate, but never silent. Fail-closed on a missing locale would drop rows on every host that does not pass one — a silent data-loss regression strictly worse than the over-seeding it prevents. Same posture resolveEnvConfig already takes.

The producer gap, stated plainly

The axis is evaluated against config.locale, and no first-party call site supplies one yet. The runtime wiring that would resolve it from the stack's configured locale lives in packages/runtime/src/app-plugin.ts (three seedLoader.load(request) sites) plus the draft-publish path in protocol.ts — outside this card's declared file surface, so it is not in this PR. An embedding host that passes config.locale itself gets the full behaviour today; on the default boot path the axis is inert.

That is the shape Seed.env was in before framework#4704, so two things follow and both are in this diff:

  1. warnOnUnresolvedLocaleScope — a load carrying locale-scoped datasets and no config.locale warns naming every dataset it let through and the config key that would make the scope take effect. This is exactly what Seed.env is authorable but never enforced: the app seeding path never sets SeedLoaderConfig.env, so env: ['dev'] seeds into production too #4704 did not have: the axis can be unwired, it cannot be silently unwired.
  2. The liveness ledger records seed.locale as experimental, not live — consumer side cited, producer gap spelled out in the row. Publishing live on a correct-but-insufficient consumer pointer is the liveness 台账把「消费端存在读取代码」当作 live 的证据,会漏掉「没有任何生产者传值」的死键(Seed.env 即如此) #4837 falsehood the producer field exists to prevent, and seed.env is the specimen it was built from. Re-classify to live with a producer pointer when the wiring lands.

Follow-up the PM should file: wire config.locale from the runtime's configured locale at the seed-load call sites in packages/runtime, and flip the ledger row. A second, genuinely separate follow-up named in the card and deliberately not attempted: reconciling rows already written under a different locale.

Verification record

Ran in a dedicated worktree at 35929cd3, everything foreground, heavy runs through scripts/pm/os-verify-lock.sh.

Dependency closure and generated artifacts

pnpm --filter '@objectstack/metadata-protocol^...' build --concurrency=2
  os-verify-lock: VERDICT command-exit 0 · held the lock 195s

pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated --fix
  ✗ 3 of 15 artifact(s) stale:  skill references · content/docs/references/** · liveness/state-counts.md
  --fix: regenerating 3 of the 3 stale artifact(s).
    ✓ gen:skill-refs   ✓ gen:docs   ✓ gen:liveness-counts
  os-verify-lock: VERDICT command-exit 0

Tests

pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2 \
    src/seed-loader-locale-scope.test.ts src/seed-loader-env-scope.test.ts
  Test Files  2 passed (2)
       Tests  29 passed (29)

pnpm --filter @objectstack/spec test && pnpm --filter @objectstack/spec typecheck
  Test Files  460 passed (460)
       Tests  12778 passed (12778)
  os-verify-lock: VERDICT command-exit 0 · held the lock 354s

pnpm --filter @objectstack/metadata-protocol test
  Test Files  170 passed | 2 skipped (172)
       Tests  2448 passed | 10 skipped (2458)

pnpm --filter @objectstack/metadata-protocol typecheck
  os-verify-lock: VERDICT command-exit 0

Reverse verification (ablation). Subject resolves through SOURCE — the test imports ./seed-loader.js, a relative specifier inside the same package — so no dist leg is involved. Mutation: an early return true inserted above the normalizeLocaleTag(locale) read in datasetAllowsLocale, i.e. the locale predicate neutered while everything else stands.

HEAD blob: 7772349ec06deaa63b19a54a0b7f42011fcbfe5d
--- after: marker landed on disk ---
marker occurrences: 1
mutated blob: 5ea1048d3883b16f98129a8bdb00442c7053aa34
--- MUTANT LEG: expect RED ---
mutant-exit=1
 Test Files  1 failed (1)
      Tests  8 failed | 8 passed (16)
--- RESTORE LEG: prove the tree is back ---
restored blob: 7772349ec06deaa63b19a54a0b7f42011fcbfe5d (HEAD: 7772349ec06deaa63b19a54a0b7f42011fcbfe5d)
marker absent: OK
--- RESTORED LEG: expect GREEN ---
restored-exit=0
 Test Files  1 passed (1)
      Tests  16 passed (16)

8 of 16 red under the mutation, byte-identical restore proven by blob hash rather than by an exit code, 16/16 green again. The 8 that stayed green are the ones that should: the unscoped-dataset cases, the no-locale-supplied cases and the env-only composition legs do not depend on this predicate.

Gate families. Derived from the diff rather than guessed: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at d621dec over 14 changed paths, 103 commands. Ran all 103. Final state: 102 pass, 1 not measurable here.

Three were red on the first pass and are fixed in this diff:

  • check:system-context-census — 6 problems. The +51 lines in seed-loader.ts moved three anchors cited from content/docs/permissions/system-context.mdx. Repaired mechanically with the gate's own --fix (3 anchors rewritten); gate now OK — 106 elevation read sites in 20 packages across 45 files, all anchored.
  • check:engine-double-contract — the new test pins 3 engine doubles the ledger had not recorded. node scripts/check-engine-double-contract.mjs --write: 756 (file, verb) row(s), 3 added or grown, 0 lost. Gate green.
  • check:skill-examples, check:dual-build-cjs-loads, check:type-check-debt — all three returned PREREQUISITE NOT MET (exit 3 for two of them), reading built output this fresh worktree had not produced. Built the workspace (turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=272 successful, 72 total) and re-ran: all three green. check:type-check-debt OOMed once at 4 GB and was re-run at 8 GB — --re-measure: OK — 5 ledger entr(ies) re-measured in 66.6s, 55 raw tsc error(s) total, none above its recorded number.

Not measurable in this checkout, and not read as a pass: node scripts/check-plugin-teardown-shape.mjs --self-test refuses on a shallow clone — cannot read the positive control at 621a487607881c66b2899b7e3477115229a156b4. git fetch --deepen 500 did not reach it. It is a checker-health self-test that judges the checker's own fixtures, not this diff, and my diff touches neither the script nor plugin teardown shapes.

Lint. The repo-wide run, not a narrowed one: pnpm lint (eslint . --no-inline-config) — VERDICT command-exit 0, 66s. No narrowing to declare.

Byte discipline. pnpm check:nul-bytes green, plus a direct scan over every file in the diff with the control-character class — no hits.

Governed surface reading

The diff touches one governed path: skills/objectstack-data/references/_index.md, +1 line, entirely generated by gen:skill-refs. It moved because data/seed.zod.ts now imports LocaleSchema from system/translation.zod.ts, which enlarges the data skill's reference closure by that one module. Two readings, as the published-skills rule requires:

  • changed file: 66 → 67 lines (+1, generated)
  • whole published skills/ package, sum of every SKILL.md: 6858 → 6858 lines (unchanged — no SKILL.md is in this diff)

No prose was written or expanded. Recorded here because a governed path is judged on the file list, not on the description.

维护者速读(草稿)

改了什么 — 给种子数据集加了 locale 过滤轴,和现有的 env 轴完全平行:schema 侧新增 Seed.locale(BCP-47 标签数组,不写 = 所有语言),loader 侧 filterByLocalefilterByEnv 合取——两条都通过才装载。

为什么改 — 一个要同时发中英文演示数据的应用,今天只能在装配 config 的时候用应用自己发明的环境变量二选一。选择被固化进 dist(换语言要删构建产物),而且因为每份都是 upsert、loader 只写不清,换过去之后另一份的行还留在库里。这两件事都是"在错误的层做过滤"的后果;放到装载层,两件都自然消失。

风险与代价(含回滚) — 纯新增可选键,现有种子的行为字节不变:不写 locale 的数据集在任何语言下照常装载。风险集中在一处:runtime 还没有把配置里的语言传给 loader(那三个调用点在 packages/runtime,不在本卡的文件面内),所以默认启动路径上这条轴目前是空转的。这不是静默空转——带 locale 的数据集遇不到 config.locale 时 loader 会点名告警;liveness 台账也按 experimental 而非 live 记账,理由写在行里。回滚成本低:整个改动是可选键 + 一个过滤函数,revert 即可,没有数据迁移。

席位意见 — (留空,待席位定稿)

你要做的 — 决定 runtime 接线那半是否立即立卡(报告里已建议)。除此之外这张卡按 Clause-② 走契约评审,skills/** 有一行生成的登记行,按 Prime Directive #14 属受管面。


Generated by Claude Code

WIP — schema + loader halves; tests, docs and changeset to follow.

Claude-Session: https://claude.ai/code/session_013r78utTbiWqxghcuRJxfZf
Co-authored-by: Claude <noreply@anthropic.com>
…oubles

Both are mechanical, gate-driven repairs of this change's own side effects:
the +51 lines in seed-loader.ts moved three cited anchors, and the new
locale-scope test pins engine doubles the contract ledger had not recorded.

Claude-Session: https://claude.ai/code/session_013r78utTbiWqxghcuRJxfZf
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the size/l label Sep 7, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling labels Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/metadata-protocol, @objectstack/spec, touching 11 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/spec/authorable-surface/data.json, packages/spec/liveness/seed.json, packages/spec/liveness/state-counts.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/seed-data.mdx (via SeedSchema (symbol, a top-level const))
  • content/docs/data-modeling/validation.mdx (via SeedLoaderService (symbol, a top-level class))
  • content/docs/protocol/objectql/state-machine.mdx (via SeedLoaderService (symbol, a top-level class))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx (via SeedLoaderService (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/authorable-surface/data.json, packages/spec/liveness/seed.json, packages/spec/liveness/state-counts.md) — pages documenting those are invisible to this run
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 7862fb711be2819b767fa28b05a036fc1172a50apackageMentionDocs.

Which tree this was computed on

This run read content/docs from db172b034abe7e683df740f10482cf0d437391aa — the merge of head fa5d801454c5e65c811b354ead9dba892fa570cf into base 7862fb711be2819b767fa28b05a036fc1172a50a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin db172b034abe7e683df740f10482cf0d437391aa && git checkout db172b034abe7e683df740f10482cf0d437391aa
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7862fb711be2819b767fa28b05a036fc1172a50a fa5d801454c5e65c811b354ead9dba892fa570cf && git checkout -B drift-repro 7862fb711be2819b767fa28b05a036fc1172a50a && git merge --no-ff fa5d801454c5e65c811b354ead9dba892fa570cf

node scripts/docs-audit/affected-docs.mjs --json 7862fb711be2819b767fa28b05a036fc1172a50a

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 7862fb711be2819b767fa28b05a036fc1172a50a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Contract review PASS at CONTRACT_REVIEW_TIER#16510 comment 5571519820 (director seat, summon #17, session_01XesLUWmuhjuRwmU618AZ1M). needs:contract-review stripped from this PR in the same stroke (the card never carried it), read back. Route: GOVERNED (skills/objectstack-data/references/_index.md, 1 of 16; the generated-surface exception did not lift it) ⇒ stays draft, needs-user-decision on the PR, human merge. The author identity is one of the two authorised accounts, so this PR is assigned to hotlong in place of a review request to it; review requested from os-zhuang.

维护者速读(终稿)

改了什么:种子数据集 Seed 新增可选的 locale 过滤轴(BCP-47 标签数组),与既有的 env 轴并列;装载器按「env 且 locale 都通过」才装载。不写 locale 的数据集行为字节不变。SeedLoaderConfig.locale 是装载时传入的目标语言。同 PR 更新 spec 台账(该键记为 experimental)、文档与 changeset(@objectstack/spec / @objectstack/metadata-protocol 各 minor)。

为什么改:您在 #16510 立的卡:一个要同时发中英文演示数据的应用,今天只能在装配 config 时用自造的环境变量二选一,选择被烤进 dist、另一语言的行留在库里。把过滤放到装载层,两件事都自然消失。

风险与代价(含回滚):纯新增可选键。唯一未闭合的是 runtime 还没把配置里的语言传给装载器(#16595 已立,Blocked-by: #16510),所以默认启动路径上这条轴暂时空转——但不是静默:带 locale 的数据集在没收到 config.locale 时装载器会点名告警,并写明该传哪个键。回滚 = revert 本 PR,无数据迁移。

席位意见:契约复审达档 PASS(5571519820)。接受集扩大方向正确;locales / language / languages 是拒收带指引而非双拼写;标签匹配只做大小写折叠、不做 zhzh-CN 这类宽容回退;版本级别正确。受管面只有 1 行由 gen:skill-refs 生成的技能索引行,生成物例外因生成器未申报输出集而未生效,按 Prime Directive #14 归您人工合并。

你要做的:人工合并 PR #16592(受管面,不走队列)。是或否。


Generated by Claude Code

@hotlong
hotlong requested a review from os-zhuang September 7, 2026 13:44
@os-zhuang
os-zhuang marked this pull request as ready for review September 7, 2026 13:47
@os-zhuang
os-zhuang enabled auto-merge September 7, 2026 13:47
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Sep 7, 2026

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

出队处置(PM 审计)

队列在 14:11:43ZMERGE_CONFLICT 把本 PR 踢出。不是 CI 问题——踢出前 46 个 check 全部 success/skipped,含 Governed Surface Queue Guard(13:48:00Z success)。按 platform-readings.md 的两则成因,MERGE_CONFLICT = 兄弟卡抢先落地。

冲突签名

零配额本地试合并,git merge-tree --write-tree origin/main refs/pull/16592/head:

merge-base   f2f6684cd5e93032ed5d82a1848784b6026a8a03
origin/main  8341ed21cededfc1d4f953c54b71e76c26628ee9  (merge-base 之后 27 个提交)
PR head      35929cd379c6f35eaebe2a414b24737712d5cf4c  (未动过)

CONFLICT (content): content/docs/permissions/system-context.mdx
Auto-merging scripts/engine-double-contract.pinned.json      ← 自动合并成功,非冲突

冲突文件只有一个,两侧的体量差是这次处置的全部关键:

改动 内容
本 PR +1/−1 行号锚 metadata-protocol/src/seed-loader.ts:2032(及 :19421944)顺延为 :2083(及 :19931995)——check:system-context-census --fix 机械改出来的
origin/main +141/−113 #16614 docs(devx): anchor the isSystem census page by symbol, not by line number,把整页从行号锚迁移到符号锚

即:抢先落地的那张卡,拆掉的正是本 PR 那一行所属的整套锚点机制。本 PR 的 +51 行 seed-loader.ts 之所以会碰这个文件,只因为旧机制按行号记账;新机制按符号记账,而这 +51 行没有移动任何符号。

⚠️ 该路径带 merge=os-regen(.gitattributes:154),本 PR 触及的 os-regen 路径共三条——另两条是 skills/objectstack-data/references/_index.md(:155)与 packages/spec/liveness/state-counts.md(:141)。git merge-tree 只答默认文本合并、不套 merge driver,所以上面这份读数对 os-regen 路径系统性低报,不得读作「只有这一处要处理」。

处置路由

landing-operations.md A + B:dirty ⇒ 先同步 origin/main、整体重生成、再重挂;走机械化的 bash scripts/pm/os-regen-merge.sh,禁 rebase 与 force-push。已回原实现席位返工(本卡第 1 轮返工,上限 2 轮)。

预期解法是取 main 侧整文件后按新符号锚重跑 check:system-context-census,极可能对该文件净零改动——但这是预期不是裁决,以席位在树上跑出来的读数为准。

落地路径提醒

本 PR 属受管面(skills/objectstack-data/references/_index.md 一行生成物),契约复审已 PASS(评论 5571551413)。返工推新提交后 head 变化,原批准可能被分支保护自动 dismiss,届时需要重新批准并重新入队。这一步不在我权限内:受管面 PR 我不批准、不入队、不合并。


Generated by Claude Code

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

派发记录

返工已派出 · 2026-09-07T14:20:06Z

轮次 返工第 1 轮(上限 2 轮)
会话 session_01U4d9i4ZmjdYG2Wiq66Rx35 · mode:cloud
环境 env_01XuYuAYgtcEcgtZ2nrHPi7v
检出 / 推送 claude/issue-16510-seed-locale-axis(在既有 4 个提交上追加,非新建分支)
标签 domain:spec issue-16510 pm-dispatch rework
派发词要点 os-regen-merge.sh 四步序;禁 rebase 与 force-push;禁在 MERGE 状态跑 gen:schema;不动本卡实现;不转 draft、不入队、不挂 auto-merge、不合并

更正上一条评论的一处措辞。 我写的是「已回实现席位返工」,不准确:原实现会话 session_013r78utTbiWqxghcuRJxfZf 虽仍存活(IDLE、未归档),但本会话没有到它的直接投递通道,所以派的是新席位、新容器、从该分支重新检出。差别只在现场:新席位没有原 worktree 里已构建的产物,首轮构建耗时会更长;派发词是自足的,交付判据不变。

派发词里对冲突解法给的是建议路线(取 main 侧整文件后按符号锚重跑门禁),明确标注可换、以席位在树上跑出的读数为准——不是裁决。

我继续订阅本 PR 盯到 MERGED 或关闭。


Generated by Claude Code

…ed-locale-axis

# Conflicts:
#	content/docs/permissions/system-context.mdx

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

@os-zhuang 提醒一句,免得白等或白入队一次:14:40:03Z 这次批准落在未变的 head 上,清不掉冲突。

两次批准 commit_id 相同   35929cd  (13:47:06Z · 14:40:03Z)
head                     35929cd  仍是 4 个提交,返工席位尚未推送
base                     ffca0df  未动
mergeable_state          dirty    ← 未变

dirty 是队列入口的否决项,与批准数无关——现在重新入队会再被踢一次,签名还是 MERGE_CONFLICT

真正的闸门是那次同步提交。返工席位 14:20:06Z 派出(见上一条派发记录),现在约 20 分钟,还在克隆与首轮构建的窗口内——这张卡的门禁要跑全工作区构建,首轮偏慢属正常。我 15:10Z 复检。

推送落地后可能还要请你再批一次:若本仓分支保护开了 dismiss stale reviews,新 head 会作废现有批准。这一步我做不了——受管面 PR 我不批准、不入队、不合并。


Generated by Claude Code

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

返工验收(PM 复核)· 第 1 轮 ACCEPT

席位推了同步提交后被用量墙掐断(session_01U4d9i4ZmjdYG2Wiq66Rx35,rate_limit_info.status: rejected,重置 15:50Z),所以它欠的那条返工说明评论没写出来。代码已经落地且验证通过——下面这份读数是我在 GitHub 侧亲核的,不是转述席位自述。

状态

head              ba564fc  (5 个提交,新增 1 个同步 merge 提交)
base              8341ed2
mergeable_state   clean    ← 从 dirty 恢复
draft             false
CI                34 个 check,全部 success/skipped

关键门禁逐条(新 head 上复跑,非沿用旧读数):

Check 结论 完成时刻
Governed Surface Queue Guard success 15:02:24Z
Lint & Repo Gates success 15:26:42Z(最后一个完成)
Spec property liveness success 15:02:31Z
Check Changeset success 15:02:34Z
Type Check · workspace / source gates / consumer gates / debt ledger 4/4 success 15:04–15:18Z
Test Core (1–6 of 6) 6/6 success 15:11–15:23Z
Dogfood Regression Gate (1–3 of 3) + Verify CLI 4/4 success 15:09–15:12Z
Build Core · Build Docs · Temporal Conformance (live PG + MySQL) 3/3 success 15:07–15:10Z
Vercel(commit status) success 15:14:26Z

冲突是怎么解的 —— 用文件面证实,不是靠席位声明

我在派发词里给的建议路线是「对 system-context.mdx 取 main 侧整文件,再按新符号锚重跑门禁」,并注明可换、以树上读数为准。实际落点可以从文件面直接读出来:

返工前   16 个文件  +660 / −6
返工后   15 个文件  +659 / −5

少掉的正是 content/docs/permissions/system-context.mdx —— 它已完全不在变更文件列表里。也就是说该文件净零改动:#16614 把整页迁到符号锚之后,本 PR 那 +51 行 seed-loader.ts 不移动任何符号,新机制下无锚可改。预期与结果一致,且这里的判据是 GitHub 的文件列表,不是我的预测。

本卡实现未被动过

剩下 15 个文件与契约复审时逐字相同:seed.zod.ts +33、seed-loader.zod.ts +18、seed-loader.ts +124/−2、seed-loader-locale-scope.test.ts +323、seed.test.ts +57,加文档、changeset、liveness 台账、authorable-surface、engine-double 台账钉,以及 skills/objectstack-data/references/_index.md 那 1 行生成的登记行。契约复审(评论 5571551413)的审查对象未变,PASS 依然成立;受管面读数也未变,仍是 1 行生成物。

更正我先前的一处预判

我此前说「新 head 可能作废现有批准」。实测没有作废:两次批准现在都仍是 APPROVED(未转 DISMISSED),挂在旧 commit 35929cd 上——本仓分支保护没有开 dismiss stale reviews。所以不需要你再批一次。

现在卡在哪

绿、干净、已批准,needs-user-decision 仍在,PR 自 15:26:42Z 起全绿约一小时未落地。按契约复审给的路由,本卡是受管面、人工合并(评论 5571551413:「人工合并 PR #16592(受管面,不走队列)。是或否。」)。

@os-zhuang 就差这一步。我做不了:受管面 PR 我不批准、不入队、不挂 auto-merge、不合并。

合并后 #16595(runtime 三个 seedLoader.load 调用点接 config.locale、台账翻 live)与 #16596(跨语言残行对账)解除 Blocked-by,我会去收口。我继续盯到 MERGED 或关闭。


Generated by Claude Code

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

返工第 1 轮:同步 origin/main + 重生成 — 冲突已解,dirty 已脱离

新 head ba564fcc(原 35929cd3),分支 claude/issue-16510-seed-locale-axis,merge 提交追加在原 4 个提交之上(现 5 个)。⛔ 无 rebase、无 force-push:推送是 35929cd3..ba564fcc 的快进,reviewer 的 checkout 与已有评审线程原样保留。

同步到哪个 main sha

同步目标 origin/main = 8341ed21cededfc1d4f953c54b71e76c26628ee9
同步前 merge-base f2f6684cd5e93032ed5d82a1848784b6026a8a03
merge 提交 ba564fccb79e9bd4c8aee6cf8f13a8bda338aea2,双亲 35929cd3 + 8341ed21
PR base sha 已随之推进到 8341ed21

走的是 bash scripts/pm/os-regen-merge.sh。脚本按设计在 step 1 停下:os-regen driver 对 MIXED 文件拒绝 defer,改走文本合并并冲突,脚本据此分类并打印了「generated, MIXED」分支的指引后 exit 1

冲突怎么解的

唯一冲突:content/docs/permissions/system-context.mdx scripts/engine-double-contract.pinned.json 自动合并成功,非冲突(与 PM 侧 merge-tree 读数一致)。

取 main 侧整文件。 理由是两侧改的根本不是同一套东西:

  • 本 PR 侧 +1/−1:seed-loader.ts:2032:2083(及 :19421944:19931995),是原会话跑 check:system-context-census --fix 机械改出来的行号锚
  • origin/main55bbd92(docs(devx): anchor the isSystem census page by symbol, not by line number #16614)把整页从行号锚迁移到符号锚(packages/metadata-protocol/src/seed-loader.ts#SEED_OPTIONS#writeDeferredReference),行号在这一页上已经不再是记账单位。

本 PR 给 seed-loader.ts 加的 +51 行没有移动任何符号,而且该 delta 通篇不含 isSystem / skipTriggers(实测 git diff f2f6684 35929cd -- packages/metadata-protocol/src/seed-loader.ts | grep 'isSystem\|skipTriggers' 零命中),所以在新机制下这一页对本 PR 是净零改动。取 main 侧后该文件与 origin/main 逐字节相同,并整个退出了 PR 的 diff:changed_files 由 16 降到 15,+660/−6+659/−5

这同时就是脚本 step 2 该做的事:两侧都动过的 os-regen 路径只有这一条,取 main 侧正是 step 2 对该情形的规定动作;分支单侧动过的 os-regen 路径(另外 6 条)保留分支字节,未被回退。Step 3 照序在任何重生成之前提交了 merge。

重生成动了哪些产物

一件都没动。 merge 提交落地后,先建依赖闭包再整链重生成:

pnpm --filter '@objectstack/metadata-protocol^...' build --concurrency=2 && pnpm --filter @objectstack/spec build
  os-verify-lock: VERDICT command-exit 0 · held the lock 529s

pnpm --filter @objectstack/spec check:generated --fix
  ✓ 15/15   migration-registry · spec-changes · upgrade-guide · meta-url-spelling · skill-docs
            skill-refs · react-blocks · authorable-surface · api-surface · export-origins
            declaration-map · docs · strictness-ledger · liveness · test-typecheck
  ✓ All 15 generated artifacts are up to date.

--fix 无一条可修,git status 全程干净 —— 所以本轮没有第二个重生成提交,merge 提交就是全部交付物。gen:schema 未在 MERGE 状态下跑过(merge 已先提交,重生成在其之后)。

A 节要求的断言

  • 兄弟单条目全在。 更硬的一条:main 在本窗口推进过的每一条 os-regen 产物,在合并树里与 origin/main 逐字节相同 —— git diff HEAD origin/main -- <main 侧动过的 14 条> 输出为空。没有任何一侧被 driver 静默吞掉。
  • 上一单实现体完好。 docs(devx): anchor the isSystem census page by symbol, not by line number #16614 的三个文件(scripts/check-system-context-census.mjsscripts/isystem-census.mjscontent/docs/permissions/system-context.mdx)对 origin/main 逐字节相同。命名旁证:stranded(feat(runtime, spec): the resume door's 400 FLOW_FAILED details carry the engine's stranded verdict #16587)仍在 content/docs/references/api/automation-api.mdx(4 处)。
  • 合并树里对 main 的剩余 os-regen 差异,恰好只有本 PR 自己的行:seed-loader.mdx +3seed.mdx +1manifest.mdx +1authorable-surface/data.json +2liveness/state-counts.md +2/−2skills/objectstack-data/references/_index.md +1。受管面读数不变:仍是那一行生成的登记行。
  • 锚点断言:pnpm --filter @objectstack/spec check:authorable-surface 绿 —— 🔒 authorable-defaults/ verified against upstream 8341ed21cede — 1211 default(s) unchanged,✅ 1586 schemas。baseRev 本轮甚至没有滞后;⛔ 未为凑相等手改任何锚点。
  • 本卡实现未被触碰: git diff 35929cd HEAD --seed.zod.ts / seed-loader.zod.ts / seed-loader.ts / 两个测试 / seed-data.mdx / liveness/seed.json / changeset 输出为空。

新 head 上的复跑读数

ba564fcc 的专用 worktree 内跑,重活走 scripts/pm/os-verify-lock.sh

check:system-context-census (含 --self-test)
  --self-test: all cases passed
  OK — 106 elevation read sites in 20 packages across 45 files, living in 89 symbol(s);
       the page cites 103 symbol(s) against 103 required, over 127 anchors and
       8 file-level citation(s); 22 declared non-read; 18 row reference(s) resolve
       to their keyed row, 2 declared unheld.

check:engine-double-contract (含 --self-test)
  OK — 826 pinned, 133 in the DEBT ledger, 3 exempt.
  758 (file, verb) row(s) held by the RETAINED ledger.

check:generated
  ✓ All 15 generated artifacts are up to date.

check:authorable-surface
  🔒 verified against upstream 8341ed21cede — 1211 default(s) unchanged
  ✅ 1586 schemas

pnpm --filter @objectstack/spec test && typecheck
  Test Files  461 passed (461)
       Tests  12809 passed (12809)          # 交付时 460/12778,增量来自 main
  tsc --noEmit: exit 0

pnpm --filter @objectstack/metadata-protocol test && typecheck
  Test Files  170 passed | 2 skipped (172)
       Tests  2448 passed | 10 skipped (2458)
  tsc --noEmit: exit 0

os-verify-lock: VERDICT command-exit 0 · held the lock 737s (12m17s) · waited 0s

派发令点名的 5 项门禁在新 head 上全部复绿,读数与交付时一致或更好。

状态与边界

mergeable_state 已从 dirty 变为 blocked —— 冲突消失,当前挡在「CI 在跑 + 需要批准」上,新 head 的 16 个 check 已启动。os-zhuang 的两次批准都记在 35929cd3 上,按分支保护会随 head 变化被 dismiss,需要重新批准(派发时已预期,非异常)。

本轮未做、也不归我做:未转 draft(本仓转 draft 会同时掉 auto-merge 与队列成员资格且不自动恢复);未入队、未挂 auto-merge、未合并 —— 本 PR 因 skills/objectstack-data/references/_index.md 命中受管面,按 Prime Directive #14 走人工合并道。content/docs/releases/** 未触碰。本卡实现零改动。

推送完毕,交回车道 PM 的落地窗口。


Generated by Claude Code

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

终态补记(上一条评论写于 CI 在跑时,状态一节已被此条取代)

新 head ba564fcc 的 CI 已全部结束:34 个 check —— 32 success / 2 skipped(Console Pin GatePacked-tarball smoke (opt-in))/ 0 failure。含 Governed Surface Queue Guard success(15:02:24Z)、Lint & Repo Gates success、四个 Type Check · 分片全绿、Test Core 6 分片全绿、Dogfood Regression Gate 3 分片全绿。commit status:state: success

mergeable_state = clean 派发令的完成判据已满足:dirtyclean,冲突消除,原本跑绿的门禁在新 head 上复跑全绿。

一处更正:上一条评论按分支保护的一般行为预判 os-zhuang 的批准会被新 head dismiss —— 实际没有被 dismiss,clean 即表明评审要求仍然满足,无需重新批准。

我这边到此为止:未入队、未挂 auto-merge、未合并、未转 draft。受管面(skills/objectstack-data/references/_index.md 一行生成物)按 Prime Directive #14 归人工合并,交回车道 PM 的落地窗口。


Generated by Claude Code

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

更正一句。 我上一条(5573413198)开头写「席位被用量墙掐断,欠的返工说明评论没写出来」—— 那是我 16:26Z 读到的状态,但席位在 15:50Z 重置后已经补交:5573421005(16:28:28Z)与 5573425857(16:29:00Z)。两条评论差 30 秒交错,我那句话在发出时就已经过时。席位交付完整,报告不欠。

对不上的只有那一句。验收结论与读数不变,而且席位的报告比我的复核多给了几条我拿不到的树上证据:

  • os-regen-merge.sh 在 step 1 按设计停下(driver 对 MIXED 文件拒绝 defer),取 main 侧正是 step 2 对该情形的规定动作;
  • merge 提交落地后 check:generated --fix 15/15 无一条可修 ⇒ 本轮没有第二个重生成提交,merge 提交就是全部交付物;
  • main 在本窗口推进过的 14 条 os-regen 产物,在合并树里与 origin/main 逐字节相同 —— 没有任何一侧被 driver 静默吞掉,这比「兄弟单条目还在」硬;
  • check:authorable-surface 绿且 baseRev 未滞后;git diff 35929cd HEAD -- 对本卡实现七个文件输出为空。

另外我和席位各自独立做出并各自更正了同一个预判(新 head 会 dismiss 掉批准)—— 两边实测都是没有 dismiss,不需要重新批准。

状态不变,22:2xZ 复核:head ba564fcc、base 8341ed21clean、非草稿、34 check 全绿、两次批准有效、未合并。自 16:29Z 起约六小时无变化。

@os-zhuang 仍只差人工合并这一步(受管面,评论 5571551413 的路由)。不再重复催;我按定点复检盯到 MERGED 或关闭。


Generated by Claude Code

@os-zhuang
os-zhuang added this pull request to the merge queue Sep 8, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 8, 2026

hotlong commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

第二次出队处置(PM)· 签名四分支判为 新签名 ⇒ ⛔ 不重投

01:48:11Zgithub-merge-queue[bot]reason: CI_FAILURE 踢出。没有任何测试失败,也没有任何 job 报 failure。

批次与位置

批次分支   queue/main/pr-16592-be92d468043c1b9e100e6e0f2c2fc9216a5aa850
父 tip     be92d468  ← 该条目的前驱;现已成为 origin/main 的 tip
PR head    ba564fcc(未动)   mergeable_state: clean

出队后本 PR 的位置被 #16573 接替(其批次 queue/main/pr-16573-be92d468… 挂在同一个父 tip 上)。当前队列父-tip 链:be92d468(main)→ 16573 → 16697 → 16380 → 16719,#16592 已不在链上

失败签名(完整)

本批次四个 merge_group workflow,三绿一非绿:

Workflow 结论
Governed Surface Guard success
Spec Liveness Check success
Lint & Type Check success
CI(run 34175875839) cancelled

那个 CI run 里 17 个 job:16 success + 1 cancelled。唯一非绿的是 Build Core(job 101905084839):

started 01:12:53Z → completed 01:47:53Z   (35.0 分钟)  conclusion: cancelled
  ✓ Set up job / Checkout / Setup Node / Setup pnpm / cache / Restore Turbo cache
  ✓ Install dependencies                       01:13:11 → 01:13:26
  ⏳ Build packages (excluding docs)            01:13:26 → (in_progress,未完成,34m27s)
  ⋯ 其后 12 个 step 全部 pending,一个都没跑到

被取消的时刻 01:47:53Z 与出队时刻 01:48:11Z 相差 18 秒。

对照组 —— 这条是判据的关键

同一 workflow、相邻批次、晚 52 秒起跑的 #16573 作对照:

Build Core 结论
#16592 35.0m cancelled,build step 未完成
#16573(对照) 8.6m success
#16592 在自己 PR head 上(15:02Z) 8.5m success

#16592 本批次内的其它 job 一切正常:Test Core (1/6) 12.1m、Dogfood Verify CLI 8.5m、Build Docs 5.6m。

⇒ 不是全仓变慢,不是本批次变慢,也不是这份代码变慢(同代码在 PR head 上 8.5 分钟)。是单个 job 卡住

初判与处置

SKILL.md 的签名四分支:

  • 不是「已知 flaky 原样重投」—— 我手上没有能匹配这条签名的台账条目;
  • 不是「已修签名再现」;
  • 不是「基上缺已合修复」—— 缺的不是代码,合并树对当前 main 干净;
  • 不是 platform-readings.md 的第三种签名(那条要求本 PR 名下没有任何 merge_group run;这里有四个);
  • ⇒ 落在 新签名:⛔ 不重投,在 PR 与 Fixes 卡各留完整签名与初判。本条即是。

初判(是初判,不是裁决):基础设施侧的单 job 停摆,不是本 PR 的代码问题。 依据是上面那张对照表——同一时间窗、同一 workflow、同一 step,对照组 8.6 分钟通过。⛔ 但「flaky」不是我能自行认定的根因,台账只有人工能升级,所以我不加表、只提请。

另按 platform-readings.md:同一 PR 第二次被踢 ⇒ 停止重投(第一次是 14:11ZMERGE_CONFLICT,那次已由真实返工修掉,非重投)。

需要你决定的

@os-zhuang 重新入队与否是你的动作,我不入队、不批准、不合并。两条路:

  1. 判为一次性 runner 停摆 ⇒ 再入队一次。 本签名的「一次重投」额度未用过;若第二次仍卡在 Build packages (excluding docs),那就不是停摆,需要按真问题重新诊断(届时我来查)。
  2. 判为需要先查清 ⇒ 我按新签名去查 Build Core 在 merge_group 腿上与 PR 腿上的差异(最可能的方向是 turbo cache 在 merge_group 分支上未命中、导致 72 个包冷全量重建),查清再入队。

PR 本身状态未变:clean、非草稿、批准有效、head ba564fcc 未动。我继续盯。


Generated by Claude Code

hotlong commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

追加对照:我上一条给的猜想被证伪,结论因此更硬

上一条(5577891324)里我把「turbo cache 在 merge_group 分支未命中 ⇒ 72 个包冷全量重建」列为最可能方向。这个方向是错的,别照它去查。

证伪的对照是 #16697 —— 和本卡一样是改 packages/spec 的卡(feat(spec): export the object-level refinement checks…),同一晚、同一 merge_group 腿:

批次 卡的性质 Build Core Build packages (excluding docs) 结论
#16592 spec 35.0m in_progress,未完成 cancelled
#16697 spec 8.4m completed 7.0m success
#16573 docs 8.6m completed 7.1m success
#16592 在自己 PR head 上 spec 8.5m completed success

改 spec 的卡在 merge_group 腿上照样 7 分钟建完。所以慢的不是「受影响集变大」、不是冷缓存、不是这份代码 —— 四个对照(其中两个是 spec 卡)全部落在 7–9 分钟,只有那一次跑了 35 分钟且没跑完。

补充一条同向读数:本卡被踢后,排在其后的四张卡 #16573#16697#16380#16719 全部依次合入(origin/main 现为 5e53d73d),队列已排空,当前无任何 gh-readonly-queue/* ref。队列本身运转正常。

修正后的初判

单次 job 停摆,与本 PR 的内容无关。 这不再是「初判」量级的猜测——四个对照把「工作量 / 缓存 / 代码」三个方向全部排除了。⛔ 仍不自行认定 flaky、不加签名台账(只有人工能升级台账),本条按「疑似新 flaky 只留提请」处理。

因此建议(决定权在你)

@os-zhuang 建议重新入队一次。 依据是上表:没有任何证据指向这份代码,而同类卡在同一条腿上正常通过。本签名的「一次重投」额度未用过。若第二次仍卡在 Build packages (excluding docs),那就不是停摆,我按真问题重新诊断,并停止再投。

PR 现状(02:21Z 复核):clean、非草稿、批准有效、head ba564fcc 未动;main 已推进到 5e53d73d(本卡 base 之后 33 个提交),本地试合并仍干净、无冲突行。入队条件齐备。


Generated by Claude Code

hotlong commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

第三次落地受阻:dirty 再现(与前两次都不同的原因)· 返工第 2 轮已派出

08:00Z 复检发现 mergeable_statecleandirty。两个读数一致(GitHub 对象 + 本地零配额试合并),head ba564fcc 未动,没人推过东西——是 main 又前进了。

冲突签名

PR head       ba564fcc(5 个提交,未动)
origin/main   30b0990787ef633cfff724951ceeee08c4fc05b9
GitHub        mergeable_state: dirty

git merge-tree --write-tree origin/main refs/pull/16592/head
  CONFLICT (content): packages/spec/liveness/state-counts.md
  Auto-merging scripts/engine-double-contract.pinned.json      ← 自动合并成功,非冲突

唯一冲突是 packages/spec/liveness/state-counts.md 的总计行,肇因是 #16784(chore(spec): re-grade ActionSchema operation / patch to live):

main 侧    | **total** | **850** | **5** | **1** | **92** | **10** | **958** |
本 PR 侧   | **total** | **848** | **6** | **1** | **92** | **12** | **959** |

这是 merge=os-regen生成表(.gitattributes:141),由 gen:liveness-counts 从台账重算,正确结果是两侧并集,由生成器算出来而非手改。

三次受阻,三个互不相同的原因 —— 值得记一笔

时刻 原因 性质
1 14:11Z MERGE_CONFLICT#16614system-context.mdx 迁到符号锚 真冲突,返工第 1 轮已修
2 01:48Z CI_FAILURE — merge_group 的 Build Core 卡 35 分钟被取消(无测试失败,四个对照 7–9 分钟通过) 单次 job 停摆,非本卡问题
3 08:00Z dirty#16784 与本卡在 state-counts.md 总计行相撞 真冲突,本轮处理

三次都不是这份代码的问题。但有一条结构性摩擦值得说清楚:本卡碰 state-counts.md,而那是每一次 liveness 台账改动都会碰的同一张生成表——只要本卡还没合,任何一张改台账的卡先落地,就会再把它撞成 dirty。同步完成到下一次台账变更之间只有一个窗口。

已派出

返工第 2 轮 · 08:47:20Z · 会话 session_01Mr75Roij7XFWE6Cn2UyBq6 · mode:cloud · 推回 claude/issue-16510-seed-locale-axis。派发词要点同上一轮:os-regen-merge.sh 四步序、禁 rebase 与 force-push、禁在 MERGE 状态跑 gen:schema、不动本卡实现、不转 draft、不入队、不挂 auto-merge、不合并;并额外要求推送优先于报告(上一轮席位推完后被用量墙掐断,报告迟了近一小时)。

⚠️ 返工第 2 轮是上限。 若这轮再出问题,按升级通道走,不再自行派第 3 轮。

@os-zhuang 的一句话

本卡的落地窗口很窄。席位推完转绿后,越早入队越好——否则下一张改 liveness 台账的卡落地,又是同一处 dirty。02:2xZ 那条关于第二次出队(单 job 停摆)的重新入队建议依然成立,这轮同步会把两件事一并解决。


Generated by Claude Code

…ed-locale-axis

Second sync round for PR #16592. Sole conflict was the generated total row of
packages/spec/liveness/state-counts.md, where main's #16784 re-grade of
ActionSchema operation/patch (planned -> live) met this branch's added
experimental seed.locale row. Resolved by taking either side of the anchor
numbers; the table is regenerated from the merged ledger in the follow-up
commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mr75Roij7XFWE6Cn2UyBq6
`gen:liveness-counts` recomputed from the merged ledger: the only stale
artifact this round. The total row is the union of both sides — main's
#16784 re-grade of ActionSchema operation/patch (planned -> live, 848 -> 850)
plus this branch's experimental `seed.locale` row (exp 5 -> 6, classified
958 -> 959).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mr75Roij7XFWE6Cn2UyBq6

hotlong commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

第二轮同步:dirty → 已消解冲突(只做同步与重生成,实现零改动)

同步到哪个 maind4401f75bbe2e9e1b6f52f9bad83e377c5c33277(fix(tooling): isolate the throwaway git repos in the two hook/driver-invoked scripts (#16811))。派发令给的 30b0990787ef 在我起步时已被 main 越过,所以按当时的 origin/main 走。

新 head fa5d801454c5e65c811b354ead9dba892fa570cf,在既有 5 个提交上追加 2 个(merge commit + 重生成),无 rebase、无 force-push,批准与评审线程保留。

冲突怎么解的

bash scripts/pm/os-regen-merge.sh。脚本在步骤 1 停下,分类为「GENERATED 文件、driver 拒绝 defer 的 MIXED 行」:

Auto-merging packages/spec/liveness/state-counts.md
CONFLICT (content): Merge conflict in packages/spec/liveness/state-counts.md
Auto-merging scripts/engine-double-contract.pinned.json      ← 自动合并,非冲突
✗ merge stopped on conflicts in GENERATED files the driver declined to defer

唯一冲突就是总计行,两侧各改了它:

live exp elsewhere dead planned classified
merge-base 8341ed2 848 5 1 92 12 958
main(#16784 把 ActionSchema operation/patch 从 planned 重定级为 live) 850 5 1 92 10 958
PR head ba564fc(本卡新增 seed.locale 一行 experimental) 848 6 1 92 12 959

按脚本的指示「anchor 数字取哪一侧都行」先取 main 的那行,没有手改任何数字,随后由生成器从合并后的台账重算。

步骤 2 的按文件判定另行核过,结论是它这一轮无事可做:对 merge-base 取两侧的 os-regen 路径 diff,两侧同改的只有 state-counts.md 一条(即已经冲突暴露出来的那条),不存在 driver 静默吞掉某一侧的第二处。分支单侧改动的 5 条(content/docs/references/data/seed{,-loader}.mdxkernel/manifest.mdxauthorable-surface/data.jsonskills/objectstack-data/references/_index.md)按脚本规则保留分支字节;content/docs/permissions/system-context.mdx 这一轮只有 main 动过,git 平凡取 main 侧。步骤 3 的交接断言(所有 os-regen 路径均已提交)通过后才进入步骤 4,gen:* 全部在 MERGE 状态之外跑。

重生成动了哪些产物

pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated --fix:

✗ 1 of 15 artifact(s) stale:  liveness/state-counts.md
--fix: regenerating 1 of the 1 stale artifact(s).
  ✓ gen:liveness-counts

上一轮是 3 个 stale,这一轮只有 1 个。重算结果正是两侧的并集,与派发令的断言一致:

-| **total** | **850** | **5** | **1** | **92** | **10** | **958** |
+| **total** | **850** | **6** | **1** | **92** | **10** | **959** |

逐行核对(seed 保住本卡的 experimental,action 保住 #16784 的重定级):

base main PR head 合并后
seed 12 0 0 0 0 12 12 0 0 0 0 12 12 1 0 0 0 13 12 1 0 0 0 13
action 41 0 0 3 4 48 43 0 0 3 2 48 41 0 0 3 4 48 43 0 0 3 2 48

提交前读的是暂存区 diff(git addgit diff --cached),不是工作区 diff:整个重生成提交只有上面那一行。

复跑读数(新 head fa5d8014,重活走 scripts/pm/os-verify-lock.sh)

pnpm --filter @objectstack/spec check:generated
  ✓ All 15 generated artifacts are up to date.

pnpm --filter @objectstack/spec check:liveness
  ✓ every governed-type property ... classified ...
  ✓ packages/spec/liveness/state-counts.md is current — the same 36 row(s)

pnpm --filter @objectstack/spec check:authorable-surface
  🔒 authorable-defaults/ verified against upstream d4401f75bbe2 — 1211 default(s) unchanged (#4666)
  ✅ Successfully generated 1588 schemas.

pnpm check:system-context-census
  OK — 106 elevation read sites in 20 packages across 45 files, living in 89 symbol(s);
       the page cites 103 symbol(s) against 103 required, over 127 anchors

pnpm check:engine-double-contract
  OK — 836 pinned, 133 in the DEBT ledger, 3 exempt.
       768 (file, verb) row(s) held by the RETAINED ledger

pnpm --filter @objectstack/spec test
  Test Files  465 passed (465)
       Tests  12961 passed (12961)          os-verify-lock: VERDICT command-exit 0

pnpm --filter @objectstack/spec test && pnpm --filter @objectstack/spec typecheck
  os-verify-lock: VERDICT command-exit 0 · held the lock 358s

pnpm --filter @objectstack/metadata-protocol test && ... typecheck
  Test Files  170 passed | 2 skipped (172)
       Tests  2449 passed | 10 skipped (2459)
  os-verify-lock: VERDICT command-exit 0

check:authorable-surface 的锚点读数是 d4401f75bbe2,即合并进来的那个 main,不是分叉点——锚点前进而非倒退,这正是「不在 MERGE 状态下跑 gen:schema」要保住的那件事。

交付面没有漂移

git diff --stat d4401f75 HEAD = 15 files, 659 insertions(+), 5 deletions(-),与本 PR 原有的 15 files / +659 / −5 逐字相同。实现体(seed.zod.ts / seed-loader.zod.ts / seed-loader.ts 的 locale 逻辑、两个测试、文档正文、changeset、liveness/seed.json)在两个新提交里一字未动,content/docs/releases/** 未触及。datasetAllowsLocale / normalizeLocaleTag / filterByLocale / warnOnUnresolvedLocaleScopeliveness/seed.json"locale": { "status": "experimental" } 都已 git grep 核在。

状态

mergeable_state 已从 dirty 变为 blocked(冲突消解,余下是必需门禁/审批,不是冲突)。

推送后 main 又前进了两个提交(7862fb717f96e141),我用 driver 缺席的裸克隆探针(#15815 里唯一与 GitHub 同条件的量法)对最新 main 复测:

git --git-dir=PROBE.git merge-tree --write-tree --name-only 7f96e141 fa5d8014
  7e58c03bf2b8569d981273d1fe5d4284c043b7f8      probe-exit=0

即对 7f96e141 仍然干净——这两个提交没碰 liveness 台账。

按派发令:未转 draft、未入队、未挂 auto-merge、未合并;skills/objectstack-data/references/_index.md 一行生成物属受管面,按 Prime Directive #14 归人工合并。


Generated by Claude Code

hotlong commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

返工第 2 轮验收 · ACCEPT —— 落地窗口现在是开的

席位 session_01Mr75Roij7XFWE6Cn2UyBq6 在派发后 11 分钟推完(08:47:20Z 派出 → 08:58 CI 已在新 head 上起跑)。

状态(09:23Z 亲核)

head          fa5d8014  (原 ba564fcc,+2 个提交)
                c8607d34  Merge remote-tracking branch 'origin/main'
                fa5d8014  chore(spec): regenerate liveness state counts after the second main sync
CI            34 个 check → 32 success / 2 skipped / 0 failure
本地试合并     对 origin/main f89812e4 干净,无冲突行

关键门禁全绿:Governed Surface Queue Guard(08:58:54Z)、Spec property liveness(08:58:15Z)、Lint & Repo Gates(09:19:24Z)、四个 Type Check · 分片、Test Core 6 分片、Dogfood Regression Gate 3 分片、Check ChangesetBuild DocsTemporal Conformance

顺带把上一条签名也证死了: 这一轮 Build Core 用时 8m23s(08:57:47 → 09:06:10),与我此前四个对照的 7–9 分钟一致。01:48Z 那次 35 分钟未完成确属单次停摆,不是这份代码的性质。

冲突解得对不对 —— 用数字验,不用声明验

state-counts.md 的总计行是生成物,正确结果必须是两侧的并集。实测:

origin/main   | **total** | **850** | **5** | **1** | **92** | **10** | **958** |
新 head       | **total** | **850** | **6** | **1** | **92** | **10** | **959** |
                             ↑ #16784 的      ↑ 本卡的            ↑ 并集
                             ActionSchema     seed.locale
                             re-grade 保住     +1 保住

850 说明 #16784 那两条 live 没被本卡的旧基线冲掉;6959 说明本卡新增的 experimental 行没被 main 冲掉;10 取的是 main 侧的值。两侧贡献都在,没有任何一侧被 merge driver 静默吞掉。

本卡实现零改动: git diff ba564fcc..fa5d8014 --seed.zod.ts / seed-loader.zod.ts / seed-loader.ts / liveness/seed.json / changeset 输出为空。契约复审(评论 5571551413)的审查对象未变,PASS 依然成立;受管面读数仍是那一行生成的技能索引行。

@os-zhuang —— 现在是窗口期

三次受阻(MERGE_CONFLICT → 单 job 停摆 → state-counts.md 相撞)全部处理完,PR 现在绿且可合。但本卡碰的是每一次 liveness 台账改动都会碰的那张生成表,所以这个窗口会被下一张改台账的卡关掉——今天已经发生过一次(#16784)。

建议尽快入队。 受管面 PR 我不批准、不入队、不挂 auto-merge、不合并。

⚠️ 返工第 2 轮是上限,已用完。若再被撞成 dirty,我不再自行派第 3 轮,按升级通道走。


Generated by Claude Code

hotlong commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

已挂 auto-merge(squash)· 附一条对「受管面」判定的更正

维护者 @os-zhuang13:45Z 明确指示跟进到合并,据此执行入队动作。落地前重新核过三件事,都是当场读的,不是复用上一条的读数:

mergeable_state   clean
head              fa5d8014(未动)
CI                34 checks → 32 success / 2 skipped / 0 failure
批准              os-zhuang APPROVED ×3,最新一条在 ba564fcc,未被 stale-dismiss
对当前 main       25b07897(我 09:00Z 同步的 d4401f75 之后又 34 个提交)
                  裸克隆无 driver 探针 exit 0 —— 仍无冲突

为什么这张 PR 不是受管面 —— 判定器说了算,不是记忆

上一条验收里写的「受管面 PR 我不批准、不入队」是过严的。对最终的 15 条文件面重跑仓库自己的判定器:

node scripts/pm/check-governed-merges.mjs --test <15 paths>

governed-surface predicate: 0 of 15 path(s) hit the register after 1 generated-artifact lift(s)
  ✅  NOT governed — ordinary queue landing applies to a PR with exactly this file list.
  ℹ️  generated-surface exception (#11705): skills/objectstack-data/references/_index.md
      is a PURE REGENERATION — byte-equal to `gen:skill-refs` recomputed on THIS tree
      (never a stored baseline), so this path does not govern the PR by itself.

skills/objectstack-data/references/_index.md 那一行是 gen:skill-refs 在本树上重算的纯生成物(check:generated 报 15/15 current,即字节相等),命中 #11705 的生成物豁免,本身不使这张 PR 变成受管面。所以适用的是普通队列落地,不是 Prime Directive #14 的人工合并道。

这也正是 landing-operations.md A 节那两条的意思:「⛔ 不把该清单抄进派发令当常量」,以及判定器自己打印的「Re-run on the FINAL file list: a reading taken earlier in the session is recall」。前面三轮都是按「碰了 skills/** 就是受管面」的记忆走的,没有对最终文件面重跑判定器——这是这张 PR 多等了一个落地窗口的直接原因,值得记一笔。

动作

enable_pr_auto_merge,squash。绿且无冲突时由队列拉走;我跟到 MERGED 为止,期间若被下一张改 liveness 台账的卡再撞成 dirty,按同一套四步序再同步一次。

⛔ 未动 needs-user-decision 标签:它指向的是「runtime 接线那半是否立即立卡」这个决定,是本卡之外的后续项,不是落地闸。


Generated by Claude Code

Merged via the queue into main with commit c1d8f98 Sep 8, 2026
35 checks passed
@hotlong
hotlong deleted the claude/issue-16510-seed-locale-axis branch September 8, 2026 14:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants